package main

import (
    "go-os-qemu/pkg/keyboard"
    "go-os-qemu/pkg/vga"
    "go-os-qemu/pkg/elf"
    "go-os-qemu/pkg/pci"
    "go-os-qemu/pkg/nic"
    "go-os-qemu/pkg/netstack"
    "go-os-qemu/pkg/runtimeapi"
    _ "unsafe"
)

var cwd string = "/"
// prompt color theme
var promptFG byte = vga.ColorLightGreen
var promptBG byte = vga.ColorBlack
// simple fixed-size command history
var history [16]string
var histLen int

func prompt() string {
    // Show current directory in prompt
    if cwd == "/" {
        return "/> "
    }
    return cwd + "> "
}

func resolvePath(p string) string {
    // Absolute
    if p == "" {
        return cwd
    }
    if p[0] == '/' {
        return cleanPath(p)
    }
    // Relative
    base := cwd
    if base == "/" {
        return cleanPath("/" + p)
    }
    return cleanPath(base + "/" + p)
}

func cleanPath(p string) string {
    // Normalize . and .. components for POSIX-style path
    if p == "" { return "/" }
    comps := make([]string, 0, 16)
    i := 0
    // Skip leading slashes
    for i < len(p) && p[i] == '/' { i++ }
    start := i
    for i < len(p) {
        if p[i] == '/' {
            if start < i {
                seg := p[start:i]
                switch seg {
                case ".":
                    // skip
                case "..":
                    if len(comps) > 0 { comps = comps[:len(comps)-1] }
                default:
                    comps = append(comps, seg)
                }
            }
            // skip consecutive slashes
            for i < len(p) && p[i] == '/' { i++ }
            start = i
        } else {
            i++
        }
    }
    // process trailing segment
    if start < i {
        seg := p[start:i]
        switch seg {
        case ".":
            // skip
        case "..":
            if len(comps) > 0 { comps = comps[:len(comps)-1] }
        default:
            comps = append(comps, seg)
        }
    }
    // Join
    out := "/"
    for idx, c := range comps {
        if idx != 0 { out += "/" }
        out += c
    }
    return out
}

func changeDir(path string) {
    // cd with no args -> root
    target := path
    if target == "" { target = "/" }
    target = resolvePath(target)
    // A directory exists if List succeeds
    items, ok := rootFS.List(target)
    if ok {
        _ = items // we don't print here
        cwd = target
        return
    }
    vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
    vga.Println("No such directory")
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
}

// main is the TinyGo entry point invoked from the multiboot stub.
func main() {
    vga.Clear()
    vga.SetColor(vga.ColorLightCyan, vga.ColorBlack)
    vga.Println("Welcome to Go-OS!")

    vga.SetColor(vga.ColorYellow, vga.ColorBlack)
    vga.Println("Running inside QEMU via TinyGo.")

    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    vga.Println("Features: colored text, cursor, newline, scroll.")
    vga.SetColor(promptFG, promptBG)
    vga.Print(prompt())
    initFS()

    // ELF 自动加载已禁用：提供交互式 shell 体验

    // Initialize keyboard to ensure scanning is enabled
    keyboard.Init()

    // Initialize netstack and register RX handler
    netstack.Init()

    // Minimal interactive terminal: echo typed keys, handle Enter and Backspace
    var buf [80]rune
    var n int
    var cur int
    var histBrowse = -1
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    for {
        ch, ok := keyboard.PollChar()
        if !ok {
            continue
        }
        switch ch {
        case '\n':
            vga.Println("")
            cmd := string(buf[:n])
            recordHistory(cmd)
            handleCommand(cmd)
            histBrowse = -1
            vga.SetColor(promptFG, promptBG)
            vga.Print(prompt())
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            n = 0
            cur = 0
        case '\b':
            if cur > 0 && n > 0 {
                // delete char before cursor
                // move cursor back visually
                vga.Print("\b")
                // shift left from cur to end
                for i := cur-1; i < n-1; i++ { buf[i] = buf[i+1] }
                n--
                cur--
                // redraw tail
                for i := cur; i < n; i++ { vga.Print(string(buf[i])) }
                // erase last leftover char on screen
                vga.Print(" ")
                // move back to cursor position
                for i := cur; i <= n; i++ { vga.Print("\b") }
            }
        case '\t':
            // Tab completion: attempt to complete current token as a path
            lineStr := string(buf[:n])
            newLine, _ := tabComplete(lineStr)
            // Redraw line
            vga.Println("")
            vga.SetColor(promptFG, promptBG)
            vga.Print(prompt())
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            // Update buffer with newLine (truncate if exceeds capacity)
            n = 0
            for i := 0; i < len(newLine) && i < len(buf); i++ {
                buf[i] = rune(newLine[i])
                n++
            }
            vga.Print(newLine)
            cur = n
        case keyboard.KeyLeft:
            if cur > 0 { vga.Print("\b"); cur-- }
        case keyboard.KeyRight:
            if cur < n { vga.Print(string(buf[cur])); cur++ }
        case keyboard.KeyHome:
            for cur > 0 { vga.Print("\b"); cur-- }
        case keyboard.KeyEnd:
            for cur < n { vga.Print(string(buf[cur])); cur++ }
        case keyboard.KeyDelete:
            if cur < n {
                // delete char at cursor
                for i := cur; i < n-1; i++ { buf[i] = buf[i+1] }
                n--
                // redraw tail
                for i := cur; i < n; i++ { vga.Print(string(buf[i])) }
                vga.Print(" ")
                for i := cur; i <= n; i++ { vga.Print("\b") }
            }
        case keyboard.KeyUp:
            if histLen > 0 {
                if histBrowse == -1 { histBrowse = histLen-1 } else if histBrowse > 0 { histBrowse-- }
                // replace current line with history[histBrowse]
                // move to line start
                for i := 0; i < cur; i++ { vga.Print("\b") }
                // clear current content visually
                for i := 0; i < n; i++ { vga.Print(" ") }
                for i := 0; i < n; i++ { vga.Print("\b") }
                // load history
                line := history[histBrowse]
                n = 0
                for i := 0; i < len(line) && i < len(buf); i++ { buf[i] = rune(line[i]); n++ }
                vga.Print(line)
                cur = n
            }
        case keyboard.KeyDown:
            if histBrowse != -1 {
                if histBrowse < histLen-1 { histBrowse++ } else { histBrowse = -1 }
                // move to start and clear
                for i := 0; i < cur; i++ { vga.Print("\b") }
                for i := 0; i < n; i++ { vga.Print(" ") }
                for i := 0; i < n; i++ { vga.Print("\b") }
                if histBrowse == -1 {
                    n = 0; cur = 0
                } else {
                    line := history[histBrowse]
                    n = 0
                    for i := 0; i < len(line) && i < len(buf); i++ { buf[i] = rune(line[i]); n++ }
                    vga.Print(line)
                    cur = n
                }
            }
        default:
            if n < len(buf) && ch >= 32 {
                // insert at cursor
                for i := n; i > cur; i-- { buf[i] = buf[i-1] }
                buf[cur] = ch
                n++
                // echo inserted char and tail
                vga.Print(string(ch))
                for i := cur+1; i < n; i++ { vga.Print(string(buf[i])) }
                // move caret back to after inserted char
                for i := cur+1; i < n; i++ { vga.Print("\b") }
                cur++
            }
    }
    }

    // Unreachable: loop above keeps CPU alive
}
//go:linkname runtimeRun runtime.run
func runtimeRun()

//go:export kernel_entry
func kernelEntry() {
    // Initialize IDT and enable hardware interrupts (timer/keyboard)
    idtInit()
    runtimeRun()
}

//go:linkname jump_to jump_to
func jump_to(entry uintptr)

//go:linkname idtInit idt_init
func idtInit()

// capture program return code from trampoline
//go:linkname runRetCode run_retcode
var runRetCode uint32

// runBinary is a reserved hook to load an ELF32 image from memory
// and report readiness to jump to its entry point. Actual jump will
// be implemented after we decide the program source and ABI.
func runBinary(image []byte) {
    entry, ok := elf.Load(image)
    if ok && entry != 0 {
        vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
        vga.Println("ELF loaded; jumping to entry...")
        jump_to(entry)
        // Show exit code from EAX captured by trampoline
        code := int(runRetCode)
        if code == 0 {
            vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
        } else {
            vga.SetColor(vga.ColorYellow, vga.ColorBlack)
        }
        vga.Print("Program returned. exit=")
        printDec(code)
        vga.Println("")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    } else {
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("ELF load failed.")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    }
}

// handleCommand parses simple commands: ls and cat.
func handleCommand(line string) {
    // trim spaces
    i := 0
    for i < len(line) && (line[i] == ' ' || line[i] == '\t') { i++ }
    line = line[i:]
    if line == "" {
        return
    }
    // history replay: !! -> last command
    if line == "!!" {
        if histLen == 0 {
            vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
            vga.Println("No history")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            return
        }
        handleCommand(history[histLen-1])
        return
    }
    // parse command
    if startsWith(line, "ls") {
        dir := ""
        if len(line) > 2 {
            j := 2
            for j < len(line) && line[j] == ' ' { j++ }
            if j < len(line) { dir = line[j:] }
        }
        listFS(dir)
        return
    }
    if startsWith(line, "cat") {
        path := ""
        if len(line) > 3 {
            j := 3
            for j < len(line) && line[j] == ' ' { j++ }
            if j < len(line) { path = line[j:] }
        }
        showFile(path)
        return
    }
    if startsWith(line, "run") {
        path := ""
        if len(line) > 3 {
            j := 3
            for j < len(line) && line[j] == ' ' { j++ }
            if j < len(line) { path = line[j:] }
        }
        if path == "" {
            vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
            vga.Println("Usage: run <path>")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            return
        }
        pid := spawnElfTask(path)
        if pid >= 0 {
            if !anyTaskRunning() { startFirstTask() }
        }
        return
    }
    if startsWith(line, "pwd") {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println(cwd)
        return
    }
    if startsWith(line, "cd") {
        path := ""
        if len(line) > 2 {
            j := 2
            for j < len(line) && line[j] == ' ' { j++ }
            if j < len(line) { path = line[j:] }
        }
        changeDir(path)
        return
    }
    if startsWith(line, "help") {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println("Commands: ls [dir], cat <path>, run <path>, cd <dir>, pwd, irq ..., clear, help, history, !!, hexdump <path> [limit], theme <name>, net ... (pci|scan|info|up|mac|rx [n]|stats|setip <ip>|arp <ip>|ping <ip>|udp <ip> <port> [text]|tcp <ip> <port>|dns <server> <name>), runtime ... (startdemo|start|ps|new <name>|kill <pid>|quantum <n>|preempt on|off|print <text>|println <text>|dec <n>|hex <n>|color <fg> <bg>|clear|eval <expr>|runfile <path>)")
        return
    }
    if startsWith(line, "clear") {
        vga.Clear()
        return
    }
    if startsWith(line, "history") {
        printHistory()
        return
    }
    if startsWith(line, "irq") {
        // Parse: irq on|off | irq kbd on|off | irq timer on|off
        j := 3
        for j < len(line) && line[j] == ' ' { j++ }
        if j >= len(line) {
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Println("Usage: irq on|off | irq kbd on|off | irq timer on|off")
            return
        }
        // token1
        t1Start := j
        for j < len(line) && line[j] != ' ' { j++ }
        t1 := line[t1Start:j]
        for j < len(line) && line[j] == ' ' { j++ }
        // token2 (optional)
        t2Start := j
        for j < len(line) && line[j] != ' ' { j++ }
        t2 := line[t2Start:j]

        switch t1 {
        case "on":
            irqPrintTimer = true
            irqPrintKeyboard = true
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Println("IRQ print: ON")
            return
        case "off":
            irqPrintTimer = false
            irqPrintKeyboard = false
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Println("IRQ print: OFF")
            return
        case "kbd":
            if t2 == "on" { irqPrintKeyboard = true } else if t2 == "off" { irqPrintKeyboard = false }
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            if irqPrintKeyboard { vga.Println("IRQ kbd: ON") } else { vga.Println("IRQ kbd: OFF") }
            return
        case "timer":
            if t2 == "on" { irqPrintTimer = true } else if t2 == "off" { irqPrintTimer = false }
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            if irqPrintTimer { vga.Println("IRQ timer: ON") } else { vga.Println("IRQ timer: OFF") }
            return
        }
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("Unknown irq command")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    if startsWith(line, "runtime") {
        j := 7
        for j < len(line) && line[j] == ' ' { j++ }
        if j >= len(line) {
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Println("Usage: runtime startdemo | runtime start | runtime ps | runtime new <name> | runtime kill <pid> | runtime quantum <n> | runtime preempt on|off | runtime print <text> | runtime println <text> | runtime dec <n> | runtime hex <n> | runtime color <fg> <bg> | runtime clear | runtime eval <expr> | runtime runfile <path>")
            return
        }
        tStart := j
        for j < len(line) && line[j] != ' ' { j++ }
        sub := line[tStart:j]
        switch sub {
        case "startdemo":
            createDemoTasks()
            startFirstTask()
            return
        case "start":
            initScheduler()
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Println("runtime started (empty)")
            return
        case "ps":
            ps()
            return
        case "new":
            for j < len(line) && line[j] == ' ' { j++ }
            nStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            name := ""
            if nStart < j { name = line[nStart:j] }
            if name == "" { vga.Println("Usage: runtime new <name>"); return }
            pid := newWorker(name)
            if pid >= 0 && !anyTaskRunning() { startFirstTask() }
            return
        case "kill":
            for j < len(line) && line[j] == ' ' { j++ }
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            pid := -1
            if pStart < j {
                if v, ok := parseDec(line[pStart:j]); ok { pid = v }
            }
            if pid < 0 { vga.Println("Usage: runtime kill <pid>"); return }
            killTask(pid)
            return
        case "quantum":
            for j < len(line) && line[j] == ' ' { j++ }
            qStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            q := -1
            if qStart < j {
                if v, ok := parseDec(line[qStart:j]); ok { q = v }
            }
            if q < 1 { vga.Println("Usage: runtime quantum <n>"); return }
            setQuantum(q)
            return
        case "preempt":
            for j < len(line) && line[j] == ' ' { j++ }
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            mode := ""
            if pStart < j { mode = line[pStart:j] }
            if mode == "on" { setPreempt(true); return }
            if mode == "off" { setPreempt(false); return }
            vga.Println("Usage: runtime preempt on|off")
            return
        case "print":
            for j < len(line) && line[j] == ' ' { j++ }
            text := ""
            if j < len(line) { text = line[j:] }
            if text == "" { vga.Println("Usage: runtime print <text>"); return }
            runtimeapi.Exec("print", []string{text})
            return
        case "println":
            for j < len(line) && line[j] == ' ' { j++ }
            text := ""
            if j < len(line) { text = line[j:] }
            if text == "" { vga.Println("Usage: runtime println <text>"); return }
            runtimeapi.Exec("println", []string{text})
            return
        case "dec":
            for j < len(line) && line[j] == ' ' { j++ }
            aStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            if aStart >= j { vga.Println("Usage: runtime dec <n>"); return }
            runtimeapi.Exec("dec", []string{line[aStart:j]})
            return
        case "hex":
            for j < len(line) && line[j] == ' ' { j++ }
            aStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            if aStart >= j { vga.Println("Usage: runtime hex <n>"); return }
            runtimeapi.Exec("hex", []string{line[aStart:j]})
            return
        case "color":
            for j < len(line) && line[j] == ' ' { j++ }
            aStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            a := line[aStart:j]
            for j < len(line) && line[j] == ' ' { j++ }
            bStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            b := line[bStart:j]
            if a == "" || b == "" { vga.Println("Usage: runtime color <fg> <bg>"); return }
            runtimeapi.Exec("color", []string{a, b})
            return
        case "clear":
            runtimeapi.Exec("clear", nil)
            return
        case "eval":
            for j < len(line) && line[j] == ' ' { j++ }
            expr := ""
            if j < len(line) { expr = line[j:] }
            if expr == "" { vga.Println("Usage: runtime eval <expr>"); return }
            ok := runtimeapi.Eval(expr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Eval failed")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "runfile":
            for j < len(line) && line[j] == ' ' { j++ }
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            path := ""
            if pStart < j { path = line[pStart:j] }
            if path == "" { vga.Println("Usage: runtime runfile <path>"); return }
            if rootFS == nil { vga.Println("FS not initialized"); return }
            full := resolvePath(path)
            data, ok := rootFS.ReadFile(full)
            if !ok { vga.Println("No such file"); return }
            start := 0
            for i := 0; i <= len(data); i++ {
                if i == len(data) || data[i] == '\n' {
                    if i > start {
                        s := string(data[start:i])
                        if len(s) > 0 && s[len(s)-1] == '\r' { s = s[:len(s)-1] }
                        if s != "" { runtimeapi.Eval(s) }
                    }
                    start = i+1
                }
            }
            return
        }
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("Unknown runtime subcommand")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    if startsWith(line, "hexdump") {
        path := ""
        limit := 1024
        if len(line) > 7 {
            j := 7
            for j < len(line) && line[j] == ' ' { j++ }
            // path token
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            if pStart < len(line) { path = line[pStart:j] }
            for j < len(line) && line[j] == ' ' { j++ }
            // optional limit token
            lStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            if lStart < j {
                if v, ok := parseDec(line[lStart:j]); ok { limit = v }
            }
        }
        hexdumpFile(path, limit)
        return
    }
    if startsWith(line, "theme") {
        name := ""
        if len(line) > 5 {
            j := 5
            for j < len(line) && line[j] == ' ' { j++ }
            if j < len(line) { name = line[j:] }
        }
        setTheme(name)
        return
    }
    if startsWith(line, "net") {
        // net pci | net scan | net info | net up | net mac | net txtest
        j := 3
        for j < len(line) && line[j] == ' ' { j++ }
        if j >= len(line) {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println("Usage: net pci | net scan | net info | net up | net mac | net txtest | net rx [n] | net stats | net setip <ip> | net setmask <ip> | net setgw <ip> | net route show | net arp <ip> | net ping <ip> | net udp <ip> <port> [text] | net udp listen <port> | net tcp <ip> <port> | net dns <server> <name> | net dhcp | net httpget <ip> <port> <path>")
        return
    }
        tStart := j
        for j < len(line) && line[j] != ' ' { j++ }
        sub := line[tStart:j]
        switch sub {
        case "pci":
            devs := pci.Enumerate()
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            for _, d := range devs {
                vga.Print("bus=")
                printDec(int(d.Bus))
                vga.Print(" dev=")
                printDec(int(d.Dev))
                vga.Print(" fn=")
                printDec(int(d.Func))
                vga.Print(" vid=")
                printHex32(uint32(d.Vendor))
                vga.Print(" did=")
                printHex32(uint32(d.Device))
                vga.Print(" class=")
                printHexByte(d.Class)
                vga.Print("/")
                printHexByte(d.Subclass)
                vga.Print("/")
                printHexByte(d.ProgIF)
                vga.Println("")
            }
            return
        case "scan":
            if e, ok := nic.FindE1000(); ok {
                vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
                vga.Print("e1000 found at bus=")
                printDec(int(e.Bus))
                vga.Print(" dev=")
                printDec(int(e.Dev))
                vga.Print(" fn=")
                printDec(int(e.Func))
                vga.Print(" BAR0=")
                printHex32(e.MMIO)
                // Try STATUS read
                vga.Print(" status=")
                printHex32(e.ReadStatus())
                vga.Println("")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No e1000 NIC found")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "info":
            if e, ok := nic.FindE1000(); ok {
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                vga.Print("e1000 MMIO base: ")
                printHex32(e.MMIO)
                vga.Println("")
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "up":
            if e, ok := nic.FindE1000(); ok {
                if e.Init() {
                    vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
                    vga.Println("NIC initialized")
                } else {
                    vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                    vga.Println("NIC init failed")
                }
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No e1000 NIC found")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "mac":
            if e, ok := nic.FindE1000(); ok {
                mac := e.GetMAC()
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                vga.Print("MAC: ")
                for i := 0; i < 6; i++ {
                    if i != 0 { vga.Print(":") }
                    printHexByte(mac[i])
                }
                vga.Println("")
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "txtest":
            if e, ok := nic.FindE1000(); ok {
                // Build a minimal broadcast Ethernet frame
                var frame [60]byte
                for i := 0; i < 6; i++ { frame[i] = 0xFF } // dst
                // src MAC unknown without init: just zero
                for i := 6; i < 12; i++ { frame[i] = 0x00 }
                frame[12] = 0x08; frame[13] = 0x00 // EtherType IPv4
                for i := 14; i < len(frame); i++ { frame[i] = 0x42 }
                if e.SendFrame(frame[:]) {
                    vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
                    vga.Println("TX posted")
                } else {
                    vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                    vga.Println("TX failed")
                }
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "rx":
            // optional count
            cnt := 4
            for j < len(line) && line[j] == ' ' { j++ }
            if j < len(line) {
                cStart := j
                for j < len(line) && line[j] != ' ' { j++ }
                if v, ok := parseDec(line[cStart:j]); ok { cnt = v }
            }
            if e, ok := nic.FindE1000(); ok {
                if e.DumpRx(cnt) == 0 {
                    vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                    vga.Println("No RX frames")
                    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                }
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "stats":
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Print("TX=")
            printDec(int(nic.TxCount))
            vga.Print(" RX=")
            printDec(int(nic.RxCount))
            vga.Println("")
            return
        case "setip":
            // parse IP argument
            for j < len(line) && line[j] == ' ' { j++ }
            aStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            ipStr := line[aStart:j]
            ip, ok := parseIP(ipStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net setip a.b.c.d")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            netstack.MyIP = ip
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Print("IP set to ")
            for k := 0; k < 4; k++ { if k != 0 { vga.Print(".") }; printDec(int(netstack.MyIP[k])) }
            vga.Println("")
            return
        case "arp":
            for j < len(line) && line[j] == ' ' { j++ }
            aStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            ipStr := line[aStart:j]
            ip, ok := parseIP(ipStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net arp a.b.c.d")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            if e, ok := nic.FindE1000(); ok {
                netstack.SendARPRequest(e, ip)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "ping":
            for j < len(line) && line[j] == ' ' { j++ }
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            ipStr := line[pStart:j]
            ip, ok := parseIP(ipStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net ping a.b.c.d")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            if e, ok := nic.FindE1000(); ok {
                netstack.SendICMPEcho(e, ip)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "udp":
            // Support: net udp listen <port> OR net udp <ip> <port> [text]
            for j < len(line) && line[j] == ' ' { j++ }
            tStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            token := line[tStart:j]
            if token == "listen" {
                for j < len(line) && line[j] == ' ' { j++ }
                pStart := j
                for j < len(line) && line[j] != ' ' { j++ }
                portStr := line[pStart:j]
                port, ok := parseDec(portStr)
                if !ok || port < 0 || port > 65535 {
                    vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                    vga.Println("Usage: net udp listen <port>")
                    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                    return
                }
                if netstack.ListenUDPLogging(uint16(port)) {
                    vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
                    vga.Print("Listening UDP ")
                    printDec(port)
                    vga.Println("")
                    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                }
                return
            }
            // net udp <ip> <port> [text]
            ipStr := token
            ip, ok := parseIP(ipStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net udp a.b.c.d <port> [text] | net udp listen <port>")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            for j < len(line) && line[j] == ' ' { j++ }
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            portStr := line[pStart:j]
            port, ok := parseDec(portStr)
            if !ok || port < 0 || port > 65535 {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net udp a.b.c.d <port> [text]")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            // optional text payload
            for j < len(line) && line[j] == ' ' { j++ }
            payload := ""
            if j < len(line) { payload = line[j:] }
            if e, ok := nic.FindE1000(); ok {
                netstack.SendUDP(e, ip, uint16(port), 40002, []byte(payload))
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "tcp":
            // net tcp <ip> <port> (send SYN)
            for j < len(line) && line[j] == ' ' { j++ }
            tStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            ipStr := line[tStart:j]
            ip, ok := parseIP(ipStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net tcp a.b.c.d <port>")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            for j < len(line) && line[j] == ' ' { j++ }
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            portStr := line[pStart:j]
            port, ok := parseDec(portStr)
            if !ok || port < 0 || port > 65535 {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net tcp a.b.c.d <port>")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            if e, ok := nic.FindE1000(); ok {
                netstack.SendTCPSyn(e, ip, uint16(port))
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "httpget":
            // net httpget <ip> <port> <path>
            for j < len(line) && line[j] == ' ' { j++ }
            iStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            ipStr := line[iStart:j]
            ip, ok := parseIP(ipStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net httpget a.b.c.d <port> <path>")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            for j < len(line) && line[j] == ' ' { j++ }
            pStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            portStr := line[pStart:j]
            port, ok := parseDec(portStr)
            if !ok || port < 0 || port > 65535 {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net httpget a.b.c.d <port> <path>")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            for j < len(line) && line[j] == ' ' { j++ }
            path := "/"
            if j < len(line) { path = line[j:] }
            if e, ok := nic.FindE1000(); ok {
                netstack.HTTPGet(e, ip, uint16(port), path)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "dns":
            // net dns <server> <name>
            for j < len(line) && line[j] == ' ' { j++ }
            dStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            srvStr := line[dStart:j]
            srvIP, ok := parseIP(srvStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net dns a.b.c.d <name>")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            for j < len(line) && line[j] == ' ' { j++ }
            nStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            name := line[nStart:j]
            if name == "" {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net dns a.b.c.d <name>")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            if e, ok := nic.FindE1000(); ok {
                netstack.SendDNSQuery(e, srvIP, name)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "dhcp":
            if e, ok := nic.FindE1000(); ok {
                netstack.StartDHCP(e)
            } else {
                vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                vga.Println("No NIC")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        case "setmask":
            for j < len(line) && line[j] == ' ' { j++ }
            mStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            maskStr := line[mStart:j]
            ip, ok := parseIP(maskStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net setmask a.b.c.d")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            netstack.Netmask = ip
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Print("Netmask set to ")
            for k := 0; k < 4; k++ { if k != 0 { vga.Print(".") }; printDec(int(netstack.Netmask[k])) }
            vga.Println("")
            return
        case "setgw":
            for j < len(line) && line[j] == ' ' { j++ }
            gStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            gwStr := line[gStart:j]
            ip, ok := parseIP(gwStr)
            if !ok {
                vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                vga.Println("Usage: net setgw a.b.c.d")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                return
            }
            netstack.Gateway = ip
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Print("Gateway set to ")
            for k := 0; k < 4; k++ { if k != 0 { vga.Print(".") }; printDec(int(netstack.Gateway[k])) }
            vga.Println("")
            return
        case "route":
            for j < len(line) && line[j] == ' ' { j++ }
            rStart := j
            for j < len(line) && line[j] != ' ' { j++ }
            mode := line[rStart:j]
            if mode == "show" {
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                vga.Print("IP="); printIP(netstack.MyIP)
                vga.Print(" mask="); printIP(netstack.Netmask)
                vga.Print(" gw="); printIP(netstack.Gateway)
                vga.Print(" dns="); printIP(netstack.DNS)
                vga.Println("")
                return
            }
            vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
            vga.Println("Usage: net route show")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            return
        }
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("Unknown net subcommand")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
    vga.Println("Unknown command")
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    }

func listFS(dir string) {
    if rootFS == nil {
        vga.Println("FS not initialized")
        return
    }
    // default to current directory if empty
    if dir == "" { dir = cwd }
    dir = resolvePath(dir)
    items, ok := rootFS.List(dir)
    if !ok {
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("No such directory")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    for _, it := range items {
        if it.IsDir {
            vga.SetColor(vga.ColorLightBlue, vga.ColorBlack)
            vga.Print(it.Name)
            vga.Print("/\n")
        } else {
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            vga.Print(it.Name)
            vga.Print(" ")
            printDec(it.Size)
            vga.Print(" bytes\n")
        }
    }
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
}

func showFile(path string) {
    if rootFS == nil {
        vga.Println("FS not initialized")
        return
    }
    if path == "" {
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("Usage: cat <path>")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    full := resolvePath(path)
    data, ok := rootFS.ReadFile(full)
    if !ok {
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("No such file")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    for i := 0; i < len(data); i++ {
        vga.Print(string(rune(data[i])))
    }
}

func runPath(path string) {
    if rootFS == nil {
        vga.Println("FS not initialized")
        return
    }
    if path == "" {
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("Usage: run <path>")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    full := resolvePath(path)
    data, ok := rootFS.ReadFile(full)
    if !ok {
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("No such file")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return
    }
    runBinary(data)
}

func printDec(n int) {
    // simple decimal printing without fmt
    if n == 0 {
        vga.Print("0")
        return
    }
    var buf [16]byte
    i := len(buf)
    for n > 0 && i > 0 {
        i--
        buf[i] = byte('0' + (n % 10))
        n /= 10
    }
    for ; i < len(buf); i++ {
        vga.Print(string(rune(buf[i])))
    }
}

// parseDec parses a non-negative decimal integer without using fmt.
func parseDec(s string) (int, bool) {
    if s == "" { return 0, false }
    n := 0
    for i := 0; i < len(s); i++ {
        c := s[i]
        if c < '0' || c > '9' { return 0, false }
        n = n*10 + int(c-'0')
    }
    return n, true
}

// parse dotted IPv4 like a.b.c.d
func parseIP(s string) ([4]byte, bool) {
    var ip [4]byte
    seg := 0
    val := 0
    have := false
    for i := 0; i < len(s); i++ {
        c := s[i]
        if c >= '0' && c <= '9' {
            have = true
            val = val*10 + int(c-'0')
            if val > 255 { return [4]byte{}, false }
        } else if c == '.' {
            if !have || seg >= 4 { return [4]byte{}, false }
            ip[seg] = byte(val)
            seg++
            val = 0
            have = false
        } else {
            return [4]byte{}, false
        }
    }
    if !have || seg != 3 { return [4]byte{}, false }
    ip[3] = byte(val)
    return ip, true
}

// print dotted IPv4 like a.b.c.d
func printIP(ip [4]byte) {
    for i := 0; i < 4; i++ {
        if i != 0 { vga.Print(".") }
        printDec(int(ip[i]))
    }
}

func printHexNibble(n uint8) {
    d := n & 0x0F
    if d < 10 { vga.Print(string(rune('0' + d))) } else { vga.Print(string(rune('A' + (d - 10)))) }
}

func printHexByte(b uint8) {
    printHexNibble(b >> 4)
    printHexNibble(b & 0x0F)
}

func printHex32(n uint32) {
    for i := 7; i >= 0; i-- {
        shift := uint32(i*4)
        printHexNibble(uint8((n >> shift) & 0xF))
    }
}

func hexdumpFile(path string, limit int) {
    if rootFS == nil { vga.Println("FS not initialized"); return }
    if path == "" { vga.SetColor(vga.ColorLightGray, vga.ColorBlack); vga.Println("Usage: hexdump <path>"); return }
    full := resolvePath(path)
    data, ok := rootFS.ReadFile(full)
    if !ok { vga.SetColor(vga.ColorLightRed, vga.ColorBlack); vga.Println("No such file"); vga.SetColor(vga.ColorLightGray, vga.ColorBlack); return }
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    // apply limit
    max := len(data)
    if limit > 0 && limit < max { max = limit }
    var off uint32 = 0
    for i := 0; i < max; i += 16 {
        // offset
        printHex32(off)
        vga.Print(": ")
        // bytes
        end := i + 16
        if end > max { end = max }
        for j := i; j < end; j++ {
            printHexByte(uint8(data[j]))
            if j != end-1 { vga.Print(" ") }
        }
        vga.Println("")
        off += uint32(end - i)
    }
    if max < len(data) {
        vga.SetColor(vga.ColorYellow, vga.ColorBlack)
        vga.Print("... truncated: shown ")
        printDec(max)
        vga.Print(" of ")
        printDec(len(data))
        vga.Println(" bytes")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    }
}

func setTheme(name string) {
    switch name {
    case "green", "default", "":
        promptFG = vga.ColorLightGreen
        promptBG = vga.ColorBlack
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println("Theme: green")
    case "cyan":
        promptFG = vga.ColorLightCyan
        promptBG = vga.ColorBlack
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println("Theme: cyan")
    case "yellow":
        promptFG = vga.ColorYellow
        promptBG = vga.ColorBlack
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println("Theme: yellow")
    case "white":
        promptFG = vga.ColorWhite
        promptBG = vga.ColorBlack
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println("Theme: white")
    default:
        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
        vga.Println("Unknown theme")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    }
}

func recordHistory(cmd string) {
    // ignore empty commands
    if cmd == "" { return }
    // avoid duplicate consecutive entries
    if histLen > 0 && history[histLen-1] == cmd { return }
    if histLen < len(history) {
        history[histLen] = cmd
        histLen++
        return
    }
    // shift left
    for i := 1; i < len(history); i++ {
        history[i-1] = history[i]
    }
    history[len(history)-1] = cmd
}

func printHistory() {
    if histLen == 0 {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Println("(empty)")
        return
    }
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    for i := 0; i < histLen; i++ {
        // print index starting from 1
        vga.Print(" ")
        printDec(i+1)
        vga.Print(": ")
        vga.Println(history[i])
    }
}

func startsWith(s, pre string) bool {
    if len(pre) > len(s) {
        return false
    }
    for i := 0; i < len(pre); i++ {
        if s[i] != pre[i] {
            return false
        }
    }
    return true
}

// tabComplete tries to complete the last token in the input line as a filesystem path.
// If exactly one match is found, it returns the updated line and true. If multiple
// matches exist, it prints the list of candidates and returns the original line.
func tabComplete(line string) (string, bool) {
    if rootFS == nil {
        return line, false
    }
    // Trim trailing spaces/tabs
    end := len(line)
    for end > 0 && (line[end-1] == ' ' || line[end-1] == '\t') { end-- }
    line = line[:end]
    // Find token start (after last space)
    start := 0
    for i := len(line) - 1; i >= 0; i-- {
        if line[i] == ' ' {
            start = i + 1
            break
        }
    }
    token := line[start:]
    // Determine directory part and name prefix within token
    dirPart := ""
    namePre := token
    slash := -1
    for i := len(token) - 1; i >= 0; i-- {
        if token[i] == '/' {
            slash = i
            break
        }
    }
    if slash >= 0 {
        dirPart = token[:slash]
        if slash+1 < len(token) {
            namePre = token[slash+1:]
        } else {
            namePre = ""
        }
    }
    dirPath := cwd
    if dirPart != "" {
        dirPath = resolvePath(dirPart)
    }
    items, ok := rootFS.List(dirPath)
    if !ok {
        return line, false
    }
    // Collect matching names
    names := make([]string, 0, len(items))
    for _, it := range items {
        if len(namePre) == 0 || startsWith(it.Name, namePre) {
            names = append(names, it.Name)
        }
    }
    if len(names) == 0 {
        return line, false
    }
    if len(names) == 1 {
        // Single match: replace token with completed name
        completed := names[0]
        if dirPart != "" {
            token = dirPart + "/" + completed
        } else {
            token = completed
        }
        return line[:start] + token, true
    }
    // Multiple matches: print candidates
    vga.Println("")
    vga.SetColor(vga.ColorLightBlue, vga.ColorBlack)
    for i := 0; i < len(names); i++ {
        vga.Print(names[i])
        if i != len(names)-1 { vga.Print("  ") }
    }
    vga.Println("")
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    return line, false
}
